Such annotations fill the entire project in Spring Boot.
But do you know what problems these annotations solve?
Why were custom annotations introduced to begin with?
How to create custom annotations?
Today, I will cover:
In Spring Boot, annotations are more than just a way to add metadata. They
Before Spring introduced custom annotations, developers had to manage configurations like email validation using XML configuration files.
The XML configuration would define beans, validators, and other necessary components to perform tasks such as validating email addresses.
Here's an example of how email validation might have been configured using XML in a Spring application:
As you can see, this can easily become a nightmare where there are hundreds of classes with many of them relying on each other.
It also meant a developer had to go look up this XML every time they had to add a new dependency.
Spring introduced custom annotations to simplify configuration by allowing developers to use annotations directly in their code.
This reduced the need for extensive XML configuration, making the codebase cleaner and easier to maintain.
Custom annotations in Spring enable a declarative approach.
Developers can use annotations like @Transactional, @Cacheable, or @Scheduled to declare desired behaviors without writing the underlying logic.
This results in more readable and maintainable code.
Spring's custom annotations, often used with Aspect-Oriented Programming (AOP), allow developers to handle cross-cutting concerns in a centralized manner.
For example, the @Transactional annotation manages transactions across multiple methods or classes without scattering transaction management logic throughout the code.
It reduces the need for boilerplate code by encapsulating common behaviours.
For instance, the @Autowired annotation simplifies dependency injection, allowing Spring to automatically inject dependencies, rather than requiring explicit constructor or setter methods
It is a different discussion whether you should be using @Autowired or not.
By abstracting configuration and cross-cutting concerns into annotations, Spring improves the readability of the code.
You and your peer developers can quickly understand the purpose of a method or class by looking at its annotations, and annotations help enforce consistency across the codebase.
Custom annotations allow developers to create their annotations tailored to specific needs, thus extending the framework's functionality in a standardized way.
This flexibility has helped Spring remain relevant and powerful across multiple applications and architectures.
package co.officegeek.tokenratelimiter; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) // Annotation available at runtime @Target(ElementType.METHOD) // Can be applied to methods public @interface LogExecutionTime { }
You can create a custom logic to process the annotation using Spring's BeanPostProcessor, Aspect, or custom annotation processing logic.
package co.officegeek.tokenratelimiter; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class LogExecutionTimeAspect { @Around("@annotation(LogExecutionTime)") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object proceed = joinPoint.proceed(); long executionTime = System.currentTimeMillis() - start; System.out.println(joinPoint.getSignature() " executed in " executionTime "ms"); return proceed; } }
Apply your custom annotation to methods, fields, or classes as defined.
package co.officegeek.tokenratelimiter; import org.springframework.stereotype.Service; @Service public class TestService { @LogExecutionTime public void serve() throws InterruptedException { // Simulate some work Thread.sleep(2000); } }
When you apply a custom annotation to a method, class, or field, the annotation itself doesn't directly cause any method to be called.
Instead, the logic associated with the annotation is typically implemented using reflection or aspect-oriented programming (AOP) in frameworks like Spring.
Here's a breakdown of how the compiler and runtime environment know what method to call when an annotation is applied:
Some annotations are handled at compile time by annotation processors.
Java's javax.annotation.processing package allows developers to create custom annotation processors that generate code, validate annotations, or even modify the abstract syntax tree (AST) of the code being compiled.
The annotation processor reads the annotations during compilation and executes code based on those annotations.
This can include generating new classes or methods that the code will use later.
The @Override annotation is a compile-time annotation that doesn't invoke a method but instead tells the compiler to check if the method actually overrides a superclass method.
Custom annotations can be processed at runtime using reflection.
The runtime system (e.g., a framework like Spring) uses reflection to detect the presence of annotations on methods, classes, or fields, and then applies the corresponding behavior.
A custom annotation like @LogExecutionTime doesn't directly trigger any method call.
Instead, an aspect or some other reflective mechanism checks for the presence of the annotation at runtime and then wraps the method call with additional logic.
In frameworks like Spring, AOP is commonly used to handle custom annotations.
AOP allows you to define "aspects" that can intercept method calls and perform additional processing before or after the method execution.
When the AOP framework (e.g. Spring AOP) detects an annotation, it triggers the execution of an advice method associated with the aspect.
This advice method contains the logic that the AOP framework executes when the annotated method is called.
A @Transactional annotation in Spring doesn't execute any logic by itself.
Instead, the Spring framework's AOP infrastructure intercepts calls to methods annotated with @Transactional and wraps them with transaction management logic.
Custom annotations are ideal for handling cross-cutting concerns like logging, security, transaction management, and caching.
These are concerns that affect multiple parts of an application but are not related to the core business logic.
The @LogExecutionTime annotation above is a good example as that can be used across all the methods and it does not have any business logic.
When you want to specify what should happen rather than how it should happen, custom annotations provide a clean and expressive way to do this.
Annotations like @Cacheable or @Retry allow developers to enable caching or retry logic declaratively, without writing the implementation code manually.
Custom annotations can simplify the integration of frameworks or libraries by hiding the complexity behind an easy-to-use annotation.
Annotations like @Autowired in Spring help in injecting dependencies without having to manually instantiate them.
When complex logic needs to be encapsulated in a reusable way, custom annotations can provide a clean API for applying this logic.
An annotation like @RateLimit could encapsulate logic to limit the number of times a method can be called, without cluttering the method's body with this logic.
If the logic is simple or only needs to be applied in a single place, creating a custom annotation is overkill and can unnecessarily complicate the code.
Annotations are statically defined at compile-time, making them unsuitable for scenarios where behaviour needs to be dynamically determined at runtime.
If a method's behaviour should change based on user input or external configuration, handling this with custom annotations can lead to complex solutions.
Core business logic should not be abstracted into custom annotations, as this can make the logic less transparent and harder to maintain.
Using an annotation to encapsulate a business process like @ProcessOrder might hide important business rules, making the code harder to understand and maintain.
If the behavior depends on complex interactions between multiple annotations, it can lead to unexpected results and make the code difficult to understand and debug.
Combining multiple custom annotations that affect the same method (e.g., @Retry, @Cacheable, @LogExecutionTime) can result in unpredictable behavior and is difficult to manage
Custom annotations often rely on reflection or proxy mechanisms, which can introduce performance overhead.
They should not be used in performance-critical sections of code.
Using a custom annotation to add logging to a method that is called millions of times in a tight loop could significantly degrade performance.
Custom annotations are perfect for handling cross-cutting concerns like logging, security, and transaction management.
They're also great for scenarios where you need to apply the same behaviour across multiple parts of your application.
However, for simple, one-off logic, or where fine-grained control and flexibility are required, custom annotations might not be the best approach.
Consider the trade-offs before you decide to implement them.
Custom annotations are a powerful tool in your Spring Boot arsenal, but like any tool, they should be used judiciously.
They offer a clean, reusable way to handle repetitive tasks and enforce consistency across your codebase.
But be mindful of the potential downsides, especially for complexity and performance.
I am launching a 10-day cohort-based course for software developers and aspiring microservices architects on designing and implementing rate-limiting service using Spring Boot and Bucket4j.
You'll learn:
✅ How to design and build a production-ready microservice
✅ In-depth knowledge of rate-limiting algorithms and their implementation
✅ Best practices in Spring Boot development, testing, and containerisation
But it is also about
✅ breaking down the project into specific tasks
✅ Being accountable to yourself
✅ Designing and Building the project right
It is targeted at software developers who want to design and develop a microservice which is a use case relevant to most companies.
It's ESPECIALLY for those earlier in their software developer career who might not have "project experience" but tons of passion and ambition.
If this is something that you think will help you or even if you are just curious to know more:
Register your interest and I will let you know the workshop details.
This was first published on my Substack. Subscribe to my Substack - Weekend Developer to get updates first.
Are you a developer who needs feedback on the code you write?
Or do you want someone to review your code so that you are doing the right things?
I help people with free code review sessions so that they can get early feedback and write better code
DM me on Twitter (X) or on LinkedIn and I will help you with your code.
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3